#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "timers.h"

#include "app_debug.h"
#include "aliyun_core.h"
#include "at_api.h"
#include "at_tools.h"
#include "dev_sim7020.h"
#include "aliyun_model.h"


#define MAX_ALI_RECV_BUF_SIZE 50
#define MAX_ALI_SEND_BUF_SIZE 60
#define MAX_ALI_SEND_RAW_SIZE 30
#define MAX_ALI_TOPIC_SIZE    100
#define ALI_RETURN_CODE_SUCCESS 200
#define ALI_MAX_STATE_CB_NUM 5

typedef enum
{
    ALI_MQTT_STATE_IDLE,
    ALI_MQTT_STATE_DEINIT,
    ALI_MQTT_STATE_INIT,
    ALI_MQTT_STATE_CONNECT,
    ALI_MQTT_STATE_SUB,
    ALI_MQTT_STATE_PUB,
    ALI_MQTT_STATE_WAIT_REPLY,
    ALI_MQTT_STATE_ERROR,
    ALI_MQTT_STATE_OFFLINE,

} ali_mqtt_state_t;

struct
{
    TaskHandle_t task_pub;          //publish task handler id
    TaskHandle_t task_sub;          //subscribe task handler id
    QueueHandle_t que_pub;          //publish data queue
    QueueHandle_t que_sub;          //subscribe data queue
    SemaphoreHandle_t sem_pub_wait; //publish and wait reply semphore
    
    ali_mqtt_state_t mqtt_state;    //mqtt state machine
    ali_data_t now_pub;             //now publish data
    uint8_t error_pub;              //publish error times

    ali_state_e state;                                      //aliyun core state:online,connecting or offline
    ali_state_callback state_cb[ALI_MAX_STATE_CB_NUM];  	//aliyun state changed callback
    uint8_t state_cb_count;                                 //the count of state changed callback 

    char *prokey;                   //aliyun product key
    char *devname;                  //aliyun device name
    char *devsecret;                //aliyun device secret

    uint8_t recv_buf[MAX_ALI_RECV_BUF_SIZE];    //receive buffer
    uint16_t rbuf_len;                          //receive data len
    uint8_t send_raw[MAX_ALI_SEND_RAW_SIZE];    //send raw data buffer
    uint16_t sraw_len;                          //send raw data length
    char send_buf[MAX_ALI_SEND_BUF_SIZE];       //send buffer size
    uint16_t sbuf_len;                          //send data len
    char topic[MAX_ALI_TOPIC_SIZE];             //mqtt topic
} alictl;

static bool is_inited = false;
static bool ali_sub_data_queue(ali_model_type_e type, uint32_t id, uint8_t *data, uint16_t len, uint32_t timeout);

/***********************************
 * @biref:the callback function of mqtt received data
 * @param topic: mqtt topic
 * @param data:mqtt received data
 * @param data_len:mqtt received data length
 * ********************************/
void ali_mqtt_recv_handler(char *topic, char *data, uint16_t data_len)
{
    ali_model_type_e model_type;
    uint32_t id;
    uint8_t *rdata;
    uint16_t rdata_len;
    

    alictl.rbuf_len = at_chars_to_hex(data, alictl.recv_buf, data_len);
    if (alictl.rbuf_len == 0)return;
    
    model_type = (ali_model_type_e)alictl.recv_buf[0];
    id = (alictl.recv_buf[4] << 24) + (alictl.recv_buf[3] << 16) + (alictl.recv_buf[2] << 8) + alictl.recv_buf[1];
    rdata=alictl.recv_buf+5;
    rdata_len=alictl.rbuf_len-5;

    ali_sub_data_queue(model_type,id,rdata,rdata_len,1000);
}
/***********************************
 * @biref:aliyun update connect state
 * @note:this function will execute the callback
 * ********************************/
static void ali_update_state(ali_state_e state)
{
    alictl.state = state;
    for(uint8_t i=0;i<alictl.state_cb_count;i++)
    {
        if(alictl.state_cb[i]!=NULL)
        {
            alictl.state_cb[i](alictl.state);
        }
    }
}

/******************************
 * @biref:The data is publish over MQTT 
 * 
 * ***************************/
static bool ali_data_publish(ali_data_t *pdata)
{
    memset(alictl.send_raw, 0, MAX_ALI_SEND_RAW_SIZE);
    /* copy type */
    alictl.sraw_len = 0;
    alictl.send_raw[alictl.sraw_len++] = pdata->type;
    /* copy id,id is big end storage */
    memcpy(&alictl.send_raw[alictl.sraw_len], &pdata->id, 4);
    alictl.sraw_len+=4;

    /* copy data */
    memcpy(&alictl.send_raw[alictl.sraw_len], pdata->data, pdata->datalen);
    alictl.sraw_len += pdata->datalen;
    alictl.sbuf_len = at_hex_to_chars(alictl.send_raw, alictl.sraw_len, (uint8_t *)alictl.send_buf, MAX_ALI_SEND_BUF_SIZE);
    if(pdata->is_reply)
    {
        memset(alictl.topic,0,sizeof(alictl.topic));
        snprintf(alictl.topic,MAX_ALI_SEND_BUF_SIZE,"%s/%s/%s/%s",TOPIC_PREFIX,alictl.prokey,alictl.devname,PUB_DOWN_REPLY_TOPIC_POSTFIX);
    }
    else 
    {
        memset(alictl.topic,0,sizeof(alictl.topic));
        snprintf(alictl.topic,MAX_ALI_SEND_BUF_SIZE,"%s/%s/%s/%s",TOPIC_PREFIX,alictl.prokey,alictl.devname,PUB_UP_TOPIC_POSTFIX);
    }
    return at_mqtt_pub(alictl.topic,alictl.send_buf,alictl.sbuf_len,ALI_PUB_TIMEOUT);
}

static bool ali_data_delete(ali_data_t *pdata)
{
    ali_data_t tmp_data;
    if (pdata->data != NULL)
    {
        vPortFree(pdata->data);
    }
    xQueueReceive(alictl.que_pub, &tmp_data, 0); //delete the queue data which have been pub
    return true;
}
/***************************
 * @biref:Flushes all publish queue data
 * 
 * ************************/
bool ali_pub_data_flush(void)
{
    if(is_inited==false)return false;
    return xQueueReset(alictl.que_pub);
}

/***************************
 * @biref:put data to piblish queue
 * 
 * ************************/
bool ali_pub_data_queue(ali_model_type_e type, uint32_t id, uint8_t *data, uint16_t len, uint32_t timeout)
{
    ali_data_t alidata;

    if(is_inited==false)return false;
    alidata.type = type;
    alidata.is_reply=false;
    alidata.id = id;
    alidata.datalen = len;
    alidata.data = pvPortMalloc(len);
    if (alidata.data == NULL)
        return false;
    memcpy(alidata.data, data, len);
    if((ALI_MODEL_PROP_SET_REPLY==type)||(ALI_MODEL_SERVICE_REPLY==type))
    {
        alidata.is_reply=true;
    }
    return xQueueSend(alictl.que_pub, &alidata, timeout);
           
}
/***************************
 * @biref:put data to subscribe queue
 * 
 * ************************/
static bool ali_sub_data_queue(ali_model_type_e type, uint32_t id, uint8_t *data, uint16_t len, uint32_t timeout)
{
    ali_data_t alidata;
    alidata.type = type;
    alidata.id = id;
    alidata.datalen = len;
    alidata.data = pvPortMalloc(len);
    if (alidata.data == NULL)
        return false;
    memcpy(alidata.data, data, len);
    return xQueueSend(alictl.que_sub, &alidata, timeout);
}
/***************************
 * @biref:aliyun publish task,process MQTT to establish connection to send data
 * 
 * ************************/
static void ali_pub_task(void *params)
{
    while (1)
    {
        switch ((uint8_t)alictl.mqtt_state)
        {
        case ALI_MQTT_STATE_INIT:
            if (at_mqtt_init() == false)
            {
                alictl.mqtt_state = ALI_MQTT_STATE_ERROR;
                break;
            }
            alictl.mqtt_state = ALI_MQTT_STATE_CONNECT;
            ali_update_state(ALI_STATE_CONNECTING);
            break;
        case ALI_MQTT_STATE_CONNECT:
            if (at_mqtt_connect(SERVER, PORT, alictl.prokey,alictl.devname, alictl.devsecret) == false)
            {
                alictl.mqtt_state = ALI_MQTT_STATE_ERROR;
                break;
            }
            alictl.mqtt_state = ALI_MQTT_STATE_SUB;
            ali_update_state(ALI_STATE_ONLINE);
            vTaskDelay(200);
            break;
        case ALI_MQTT_STATE_SUB:
            memset(alictl.topic,0,sizeof(alictl.topic));
            snprintf(alictl.topic,MAX_ALI_SEND_BUF_SIZE,"%s/%s/%s/%s",TOPIC_PREFIX,alictl.prokey,alictl.devname,SUB_UP_REPLY_TOPIC_POSTFIX);
            if (at_mqtt_sub(alictl.topic, 10000) == false)
            {
                alictl.mqtt_state = ALI_MQTT_STATE_ERROR;
                break;
            }
            memset(alictl.topic,0,sizeof(alictl.topic));
            snprintf(alictl.topic,MAX_ALI_SEND_BUF_SIZE,"%s/%s/%s/%s",TOPIC_PREFIX,alictl.prokey,alictl.devname,SUB_DOWN_TOPIC_POSTFIX);
            if (at_mqtt_sub(alictl.topic, 10000) == false)
            {
                alictl.mqtt_state = ALI_MQTT_STATE_ERROR;
                break;
            }
            alictl.mqtt_state = ALI_MQTT_STATE_PUB;
            break;
        case ALI_MQTT_STATE_PUB:
            xQueuePeek(alictl.que_pub, &alictl.now_pub, portMAX_DELAY);
            if (ali_data_publish(&alictl.now_pub) == false)
            {
                alictl.mqtt_state = ALI_MQTT_STATE_ERROR;
                break;
            }
            if (alictl.now_pub.is_reply==false)
            {
                alictl.mqtt_state = ALI_MQTT_STATE_WAIT_REPLY;
            }
            break;
        case ALI_MQTT_STATE_WAIT_REPLY:
            if (xSemaphoreTake(alictl.sem_pub_wait, ALI_REPORT_WAIT_TIMEOUT) == pdFALSE)
            {
                alictl.mqtt_state = ALI_MQTT_STATE_ERROR;
                break;
            }
            DEBUG("report data success!");
            ali_data_delete(&alictl.now_pub);
            alictl.mqtt_state = ALI_MQTT_STATE_PUB;
            break;
        case ALI_MQTT_STATE_ERROR:
            alictl.error_pub++;
            if (alictl.error_pub < 5)
            {
                alictl.mqtt_state = ALI_MQTT_STATE_DEINIT;
                break;
            }
            alictl.error_pub = 0;
            alictl.mqtt_state = ALI_MQTT_STATE_OFFLINE;
            ali_update_state(ALI_STATE_OFFLINE);
            break;
        case ALI_MQTT_STATE_OFFLINE:
            vTaskDelay(ALI_OOFLINE_RETRY_TIME);
            alictl.mqtt_state = ALI_MQTT_STATE_DEINIT;
            break;
        case ALI_MQTT_STATE_DEINIT:
            at_mqtt_deinit();
            alictl.mqtt_state = ALI_MQTT_STATE_INIT;
            break;
        default:
            break;
        }
        vTaskDelay(10);
    }
}
/***************************
 * @biref:aliyun subscribe task,process the data received by the MQTT
 * 
 * ************************/
static void ali_sub_task(void *params)
{
    ali_data_t alidata;
    uint8_t ret_code;
    while (1)
    {
        xQueueReceive(alictl.que_sub,&alidata,portMAX_DELAY);
        switch (alidata.type)
        {    
            case ALI_MODEL_PROP_REPORT_REPLY:
            case ALI_MODEL_EVENT_REPORT_REPLY:
                ret_code = alidata.data[0];
                if ((ret_code == ALI_RETURN_CODE_SUCCESS) && (alictl.now_pub.id == alidata.id))
                {
                    xSemaphoreGive(alictl.sem_pub_wait);
                }
                break;
            case ALI_MODEL_PROP_SET:
                ali_model_perporty_set_handler(alidata.data,alidata.datalen);
                break;
            case ALI_MODEL_SERVICE_SET:
                ali_model_service_set_handler(alidata.data,alidata.datalen);
                break;
            default:
                break;
        }
    }
}
/***************************
 * @biref:get aliyun core connect state
 * @return:
 * ************************/
ali_state_e ali_state_get(void)
{
    if(is_inited)return ALI_STATE_OFFLINE;
    else return alictl.state;
}

bool ali_deinit()
{
    is_inited = false;
    if (alictl.task_pub != NULL)vTaskDelete(alictl.task_pub);
    if (alictl.task_sub != NULL)vTaskDelete(alictl.task_sub);
    if (alictl.que_pub != NULL)vQueueDelete(alictl.que_pub);
    if (alictl.que_sub != NULL)vQueueDelete(alictl.que_sub);
    if (alictl.sem_pub_wait != NULL)vSemaphoreDelete(alictl.sem_pub_wait);
    
    memset(&alictl, 0, sizeof(alictl));
    return true;
}
bool ali_init(char *prokey,char* devname,char *devsecret)
{
    memset(&alictl, 0, sizeof(alictl));
    ali_update_state(ALI_STATE_OFFLINE);
    alictl.prokey=prokey;
    alictl.devname=devname;
    alictl.devsecret=devsecret;

    /*  create queue and semphore */
    alictl.que_pub = xQueueCreate(30, sizeof(ali_data_t));
    if (alictl.que_pub == NULL)
    {
        goto ALI_INIT_ERROR;
    }
    alictl.que_sub = xQueueCreate(30, sizeof(ali_data_t));
    if (alictl.que_sub == NULL)
    {
        goto ALI_INIT_ERROR;
    }
    alictl.sem_pub_wait = xSemaphoreCreateBinary();
    if (alictl.sem_pub_wait == NULL)
    {
        goto ALI_INIT_ERROR;
    }


    /* create task */
    if (xTaskCreate(ali_pub_task, "ali_up_task", 500, NULL, 5, &alictl.task_pub) != pdPASS)
    {
        goto ALI_INIT_ERROR;
    }
    if (xTaskCreate(ali_sub_task, "ali_down_task", 128, NULL, 3, &alictl.task_sub) != pdPASS)
    {
        goto ALI_INIT_ERROR;
    }

    /* register AT device */
    if (at_dev_register(&dev_sim7020) == false)
    {
        DEBUG("at dev register fail!");
        goto ALI_INIT_ERROR;
    }

    at_mqtt_recv_register(ali_mqtt_recv_handler);
    alictl.mqtt_state = ALI_MQTT_STATE_INIT;
    is_inited = true;
    return true;

ALI_INIT_ERROR:
    ali_deinit();
    while (1)
    {
        vTaskDelay(1000);
    }
}

/***************************
 * @biref:Register the state change callback function
 * 
 * ************************/
bool ali_state_register(ali_state_callback cb)
{
    if(is_inited==false)return false;
    if(cb==NULL)return false;
    if(alictl.state_cb_count>=ALI_MAX_STATE_CB_NUM)return false;    
    alictl.state_cb[alictl.state_cb_count++]=cb;
	return true;
}

/***************************
 * @biref:This function is called when a set property is received
 * 
 * ************************/
__weak void ali_model_perporty_set_handler(uint8_t *data,uint16_t len){}

/***************************
 * @biref:This function is called when a set service is received
 * 
 * ************************/
__weak void ali_model_service_set_handler(uint8_t *data,uint16_t len){}
	
